package bootstrap

import (
	"github.com/gin-gonic/gin"
	"html/template"
	"lottery/conf"
	"lottery/cron"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// 项目初始化文件

// 定义一个配置器类型
type Configurator func(bootstrapper *Bootstrapper)

type Bootstrapper struct {
	*gin.Engine // 内嵌扩展已有类型
	AppName     string
	AppOwner    string
	AppSpawDate time.Time
}

// 创建Bootstrapper 对象
func New(appName, appOwner string, cfgs ...Configurator) *Bootstrapper {
	b := &Bootstrapper{
		gin.Default(), // 自带两个中间件
		appName,
		appOwner,
		time.Time{},
	}
	// 加载配置
	for _, cfg := range cfgs {
		cfg(b)
	}
	// 返回 b
	return b
}

// 定义方法，查找所有web/public/(StaticAssets)目录下的 .html 文件
func getHtmlFiles(dir string) []string {
	var files []string
	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if strings.HasSuffix(path, ".html") {
			files = append(files, path)
		}
		return nil
	})

	return files
}

// 设置模板引擎目录
func (b *Bootstrapper) SetupViews(viewDir string) {
	// 给模板增添两个方法： 时间格式转换，
	b.SetFuncMap(template.FuncMap{
		"FromUnixtimeShort": func(t int) string {
			dt := time.Unix(int64(t), int64(0))
			return dt.Format(conf.SysTimeFormShort)
		},
		"FromUnixtime": func(t int) string {
			dt := time.Unix(int64(t), int64(0))
			return dt.Format(conf.SysTimeForm)
		},
	})

	//b.LoadHTMLGlob(viewDir)
	// 注意！！：自定义模板函数， 一定要放在 模板加载之后，才能起作用！
	files := getHtmlFiles(viewDir)
	b.LoadHTMLFiles(files...) // 加载视图文件
}

// 定义错误的结构体
type Error struct {
	App        string `json:"app"`
	StatusCode int    `json:"code"`
	Msg        string `json:"msg"`
}

func (e *Error) Error() string { // 实现error接口
	return e.Msg
}

// 设置异常处理
func (b *Bootstrapper) SetupErrorHandler() {
	// 定义错误返回界面，NoMethod()， NoRoute()
	var defaultErrorHandler gin.HandlerFunc
	defaultErrorHandler = func(c *gin.Context) {
		msg := "无效请求路径 或 处理方法"
		if length := len(c.Errors); length > 0 {
			msg = c.Errors[length-1].Err.Error()
		}
		// 1. 初始化一个 错误
		err := Error{
			b.AppName,
			c.Writer.Status(),
			msg,
		}
		// 2. 判断请求是否是 json格式的。
		j := c.Request.Header.Get("Content-Type")
		if j == "application/json;charset=UTF-8" {
			c.JSON(http.StatusOK, err) // 返回信息
		}
		// 3. 否则返回模板数据
		c.HTML(http.StatusOK, "error.html", err)
	}

	b.NoMethod(defaultErrorHandler)
	b.NoRoute(defaultErrorHandler)
}

// 配置方法: 这个函数用处还是很大的，比如说初始化的时候，需要配置路由信息进去Get，POST等等（路由在 routers 文件夹完善）。
//然后身份认证的时候，这个 应用引擎应当还要支持 认证的功能（中间件， middleware 中完成）。
func (b *Bootstrapper) Configure(cs ...Configurator) {
	for _, c := range cs {
		c(b)
	}
}

// 计划任务程序:
func (b *Bootstrapper) setupCorn() {
	// 初始化加载：自动检查是否需要重新计算发奖计划
	cron.ConfigAppOneCron()
}

// 定义常量，站点对外的目录
const (
	StaticAssets = "./public/"
	Favicon      = "favicon.ico"
)

// 外部创建完毕 b 后，只需要调用这一个方法，完成配置设定。
func (b *Bootstrapper) Bootstrap() *Bootstrapper {
	b.SetupViews("./views/")
	b.SetupErrorHandler()
	b.StaticFile(Favicon, StaticAssets+"favicon.ico")         // 寻找网站图标
	b.Static(StaticAssets[1:len(StaticAssets)], StaticAssets) // 设置公共静态文件访问路径
	b.setupCorn()
	return b
}
